module server.socket;

pragma(lib,"ws2_32.lib");
import std.stdio;
import std.string;
import std.c.windows.windows;
import std.c.windows.winsock;
import winapi.winsock2;

alias std.string.toString to_s;

class SocketException : Exception{
	public this(char[]msg) {
		super(std.string.format("(%d,%d) %s", GetLastError(), WSAGetLastError(), msg));
	}
}

enum SocketState 
{
	LISTENING,
	CONNECTING,
	CONNECTED,
	CLOSING,
	CLOSED
}

class Socket {
	///class methods/properties start here
	static this() {
		WSADATA wsa;
		WSAStartup(0x0202, &wsa);
	}
	
	static ~this() {
		WSACleanup();
	}

	private static HANDLE[64] events;
	private static Socket[64] socks;
	private static int sock_count = 0;
	
	private static void signaled(int idx) {
		WSAEVENT e = events[idx];
		Socket s = socks[idx];
		WSANETWORKEVENTS events;
		int r;
		
		r = WSAEnumNetworkEvents(s.s, e, &events);
		if (r == SOCKET_ERROR)
			throw new SocketException("WSAEnumNetworkEvents failed");
		
		///ACCEPT event
		if (events.lNetworkEvents & FD_ACCEPT) {
			if (events.iErrorCode[FD_ACCEPT_BIT] == 0) 
			{
				sockaddr_in s_addr;
				int sz = s_addr.sizeof;
				SOCKET c_sock = accept(s.s, cast(sockaddr *)&s_addr, &sz);
				if (c_sock == SOCKET_ERROR)
					throw new SocketException("accept() failed");
				Socket c = s.accepting(c_sock);
				c.state = SocketState.CONNECTED;
				c.on_connect(0);
			}
			else 
			{
				//TODO: remove debug statement
				writefln("DEBUG: error on accept: " ~ to_s(events.iErrorCode[FD_ACCEPT_BIT]));
			}
		}
		
		///CONNECT EVENT
		if (events.lNetworkEvents & FD_CONNECT) {
			if (events.iErrorCode[FD_CONNECT_BIT] == 0)
				s.state = SocketState.CONNECTED;
			else
				s.state = SocketState.CLOSED;
			
			s.on_connect(events.iErrorCode[FD_CONNECT_BIT]);
		}
		
		///READABLE event
		if (events.lNetworkEvents & FD_READ) {
			if (events.iErrorCode[FD_READ_BIT] != 0)
			{
				s.state = SocketState.CLOSED;
				//TODO: should this be handled differently?
				throw new SocketException("FD_READ error = " ~ to_s(events.iErrorCode[FD_READ_BIT]));
			}
			//determine how many bytes we can read from the socket
			uint bytes = s.available_bytes();
			ubyte[] buf = new ubyte[bytes];
			
			//attempt to read all data from the socket
			r = std.c.windows.winsock.recv(s.s, cast(char *)buf.ptr, cast(int)bytes, 0);
			if (r == 0) {
				//TODO: remove debug statements
				writefln("recv() failed: connection gracefully closed");
				s.close();
			} else
			if (r == SOCKET_ERROR) {
				s.state = SocketState.CLOSED;
				//TODO: should this be handled differently?
				throw new Exception("recv() failed");
			} else
			{
				//pass read data onto our callback
				s.on_read(buf[0..r]);
			}
		}
		
		///WRITEABLE event
		if (events.lNetworkEvents & FD_WRITE) {
			s.state = SocketState.CONNECTED;
			if (events.iErrorCode[FD_WRITE_BIT] == 0)
				s.on_write();
			else
			{
				//TODO: remove debug statement
				s.state = SocketState.CLOSED;
				writefln("DEBUG: error on write: " ~ to_s(events.iErrorCode[FD_WRITE_BIT]));
			}
		}
		
		///CLOSE EVENT
		if (events.lNetworkEvents & FD_CLOSE) {
			s.state = SocketState.CLOSED;
			s.on_close(events.iErrorCode[FD_CLOSE_BIT]);
			remove_sock(idx);			
			closesocket(s.s);
		}
		
		
	}
	
	private static void remove_sock(int idx) {
		//null the connection out of the sock & event list.
		//the run() function will clean up the list later
		events[idx] = null;
		socks[idx] = null;
	}
	
	//run() should be called by the application in a thread
	//that will be responsible for all socket IO
	//run() does not return.
	public static void run() {
		int r;
		
		init:
			while (sock_count < 1)
				Sleep(50);
		while (true)
		{			
			//remove stale sockets
			for (r = 0; r < sock_count; r++) {
				if (events[r] == null) {
					//remove the socket, which replaces the current idx with the last one
					//run the iteration again (this time over what was the last element)
					events[r] = events[sock_count-1];
					socks[r] = socks[sock_count-1];
					sock_count--;
				}
			}
			
			if (sock_count < 1)
				goto init;

			r = WSAWaitForMultipleEvents(sock_count, events.ptr, FALSE, 50, TRUE);
			if (r == WSA_WAIT_TIMEOUT)
				continue;
			if (r == WSA_WAIT_FAILED)
			{
				writefln("WSAWaitForMultipleEvents(%d, %X, FALSE, 50, FALSE);", sock_count, events.ptr);
				throw new SocketException("WSAWaitForMultipleEvents() failed");
			}
			
			//check all events that may have been signaled
			while (r < sock_count) {
				if (WaitForSingleObject(events[r], 0) == WAIT_OBJECT_0) {
					//event signaled
					signaled(r);
				}
				r++;
			}
		}
	}
	
	///instance methods/properties start here
	private SOCKET s;
	private int idx;
	private ubyte[] write_buf;
	private Object write_lock;
	public SocketState state;
	
	//this constructor is only used when accepting an incoming connection
	protected this(SOCKET s) {
		if (sock_count >= 64)
			throw new SocketException("No more events available");
		
		state = SocketState.CONNECTED;
		this.s = s;
		write_lock = new Object();
		idx = sock_count;
		events[idx] = WSACreateEvent();
		socks[idx] = this;		
		sock_count++;
		
		int r = WSAEventSelect(s, events[idx], FD_READ | FD_WRITE | FD_CLOSE);
		if (r == SOCKET_ERROR)
			throw new SocketException("WSAEventSelect() failed");
	}
	
	//this function needs to be overloaded by any subclass of Socket
	protected Socket accepting(SOCKET s)
	{
		return new Socket(s);
	}
	
	public this() {
		if (sock_count >= 64)
			throw new SocketException("No more events available");
		
		state = SocketState.CLOSED;
		s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		idx = sock_count;
		//add socket to event list and socket list
		events[idx] = WSACreateEvent();
		socks[idx] = this;		
		sock_count++;
		
		int r = WSAEventSelect(s, events[idx], FD_ACCEPT | FD_READ | FD_WRITE | FD_CONNECT | FD_CLOSE);
		if (r == SOCKET_ERROR)
			throw new SocketException("WSAEventSelect() failed");
	}
	
	public void connect(char []host, ushort port) {
		sockaddr_in saddr;
		saddr.sin_addr.s_addr = inet_addr(host.toStringz());
		saddr.sin_port = htons(port);
		saddr.sin_family = AF_INET;
		saddr.sin_zero[] = 0;
		
		int r = std.c.windows.winsock.connect(s, cast(sockaddr *)&saddr, saddr.sizeof);
		if ((r == SOCKET_ERROR) && (WSAGetLastError() != WSAEWOULDBLOCK))
			throw new SocketException("connect() failed");

		state = SocketState.CONNECTING;
	}
	
	public void listen(ushort port, int backlog) {
		sockaddr_in saddr;
		int r;
		
		saddr.sin_addr.s_addr = INADDR_ANY;
		saddr.sin_port = htons(port);
		saddr.sin_family = AF_INET;
		saddr.sin_zero[] = 0;
		r = std.c.windows.winsock.bind(s, cast(sockaddr *)&saddr, saddr.sizeof);
		if (r == SOCKET_ERROR)
			throw new SocketException("bind() failed");
		r = std.c.windows.winsock.listen(s, backlog);
		if (r == SOCKET_ERROR)
			throw new SocketException("listen() failed");
		state = SocketState.LISTENING;
	}
	
	public void close() {
		/*
		 * close simply initiates a graceful disconnect.
		 * we notify our peer that we are done sending data
		 * they will receive an FD_CLOSE notification and will
		 * then send any remaining data before shutting down sending on
		 * their end as well.  At that point we receive an FD_CLOSE
		 * notification and close the socket
		 */
		state = SocketState.CLOSING;
		std.c.windows.winsock.shutdown(s, SD_SEND);
	}
	
	public uint available_bytes() {
		uint bytes;
		int r;
		
		r = ioctlsocket(s, FIONREAD, &bytes);
		if (r == SOCKET_ERROR)
			throw new SocketException("ioctlsocket() failed");
		return bytes;
	}
	
	
	public char[] remote_addr()
	{
		sockaddr_in saddr;
		int saddr_sz = saddr.sizeof;
		getpeername(s, cast(sockaddr *)&saddr, &saddr_sz);

		//get the remote socket address
		char*addr = inet_ntoa(saddr.sin_addr);
		/*
		 return the LOCAL socket port though.
		saddr_sz = saddr.sizeof;
		getsockname(s, cast(sockaddr *)&saddr, &saddr_sz);
		 */

		//[remotehost]:[port]
		char[] res = std.string.format("%s:%d", std.string.toString(addr), htons(saddr.sin_port));
		
		return res;
	}
	
	public SOCKET sock()
	{
		return s;
	}
	
	public int shutdown(int sd_how)
	{
		return .shutdown(s, sd_how);
	}
	
	public int send(ubyte[] buf)
	{
		int r = std.c.windows.winsock.send(s, buf.ptr, buf.length, 0);
		if ((r == SOCKET_ERROR) && (WSAGetLastError() != WSAEWOULDBLOCK))
			throw new SocketException("send failed");
		return r;
	}
	
	protected void write_all() {
		int r;
		int s_len;
		
		if (state != SocketState.CONNECTED)
			return;
		
		//synchronized (write_lock)
		{
			while (true) {
				if (write_buf.length > 2048)
					s_len = 2048;
				else
					s_len = write_buf.length;
				
				r = send(write_buf[0..s_len]);
				if (r == SOCKET_ERROR) {
					if (WSAGetLastError() != WSAEWOULDBLOCK)
						throw new SocketException("send failed");
					else
						break;
				}			
				if (r == 0)
					break;
				write_buf = write_buf[r..$];
			}
		}
	}
	
	public void write(ubyte[] buf) {
		//synchronized (write_lock) 
		{
			write_buf ~= buf;
			write_all();
		}		
	}
	
	protected void on_read(ubyte[] buf) {
		
	}
	
	protected void on_write() {
		write_all();
	}
	
	protected void on_close(int err) {
		
	}
	
	protected void on_connect(int err) {
	}
}

public uint to_int(ubyte[]buf)
{
	return *(cast(uint *)buf.ptr);
}

public ubyte[] get_bytes(ushort i16)
{
	ushort p = i16;
	return (cast(ubyte*)&p)[0..2].dup;
}

public ubyte[] get_bytes(uint i32) {
	uint p = i32;
	return (cast(ubyte*)&p)[0..4].dup;
}